#include <QtTest>
#include "../tests_common.h"

#include <QDomDocument>
#include <QDomElement>

#include "common.h"
#include "row_text.h"
#include "rows.h"
#include "columns.h"
#include "model.h"
#include "from_xml.h"


using namespace BitWin;


Q_DECLARE_METATYPE(QDomElement)

typedef HeaderRow* PHeaderRow;
Q_DECLARE_METATYPE(PHeaderRow)

typedef WordRow* PWordRow;
Q_DECLARE_METATYPE(PWordRow);

typedef FieldRow* PFieldRow;
Q_DECLARE_METATYPE(PFieldRow)

typedef BitsColumn* PBitsColumn;
Q_DECLARE_METATYPE(PBitsColumn)


namespace QTest
{

// Memory leakages of toString routine result are not
// my business (see QtTest manual).

template<class T>
char* bitWinToString(const T& item)
{
    return qstrdup(QByteArray(item.repr().toAscii()).data());
}

template<>
char* toString(const HeaderRow& row)
{
    return bitWinToString<HeaderRow>(row);
}

template<>
char* toString(const FieldRow& row)
{
    return bitWinToString<FieldRow>(row);
}

template<>
char* toString(const BitsColumn& column)
{
    return bitWinToString<BitsColumn>(column);
}

} // QTest namespace end


class TestBitWinXml: public QObject
{
    Q_OBJECT

    // Helper method: create XML document, given by content
    // ensure that document is valid.
    QDomDocument createDoc(const QString& content);

private slots:

    void testMakeHeaderRow_data();
    void testMakeHeaderRow();

    void testMakeWordRow_data();
    void testMakeWordRow();

    void testMakeFieldRow_data();
    void testMakeFieldRow();
    
    void testMakeBitsColumn_data();
    void testMakeBitsColumn();

    void testMakeModelHeaders_data();
    void testMakeModelHeaders();

    void testMakeModelData_data();
    void testMakeModelData();
};


QDomDocument TestBitWinXml::createDoc(const QString& content)
{
    QDomDocument doc;
    bool content_ok = doc.setContent(content);
    Q_ASSERT(content_ok);
    return doc;
}

void TestBitWinXml::testMakeHeaderRow_data()
{
    QTest::addColumn<QDomElement>("xml_data");
    QTest::addColumn<PHeaderRow>("expected");
    
    QDomDocument doc(createDoc(
        "<?xml version='1.0' encoding='UTF-8'?>"
        "<headers>"
            "<header title='Bit numbers:'/>"
            "<header title='Header:'/>"
            "<header/>"
        "</headers>"
    ));
    
    QDomElement el(doc.documentElement().firstChildElement("header"));
    QTest::newRow("Header of 8 items")
        << el << new HeaderRow("Bit numbers:")
    ;
    
    el = el.nextSiblingElement("header");
    QTest::newRow("Header of 5 items")
        << el << new HeaderRow("Header:")
    ;
    
    el = el.nextSiblingElement("header");
    QTest::newRow("Header with no title specified")
        << el << new HeaderRow(QString())
    ;
}

void TestBitWinXml::testMakeHeaderRow()
{
    QFETCH(QDomElement, xml_data);
    QVERIFY(!xml_data.isNull());
    
    HeaderRow* row = FromXml::makeHeaderRow(xml_data);
    QVERIFY(row);
    
    QFETCH(PHeaderRow, expected);
    QCOMPARE(*row, *expected);
    
    delete row;
    delete expected;
}

void TestBitWinXml::testMakeWordRow_data()
{
    QTest::addColumn<QDomElement>("xml_data");
    QTest::addColumn<int>("word_no");
    QTest::addColumn<PWordRow>("expected");
    
    QDomDocument doc(createDoc(
        "<?xml version='1.0' encoding='UTF-8'?>"
        "<words>"
            "<word title='Word number x'/>"
            "<word title='Word number y'/>"
            "<word/>"
        "</words>"
    ));
    
    QDomElement el(doc.documentElement().firstChildElement("word"));
    QTest::newRow("Case 0")
        << el << 0
        << new WordRow("Word number x", 0)
    ;
    
    el = el.nextSiblingElement("word");
    QTest::newRow("Case 1")
        << el << 12
        << new WordRow("Word number y", 12)
    ;
    
    el = el.nextSiblingElement("word");
    QTest::newRow("Word with no title specified")
        << el << 13
        << new WordRow(QString(), 13)
    ;
}

void TestBitWinXml::testMakeWordRow()
{
    QFETCH(QDomElement, xml_data);
    QVERIFY(!xml_data.isNull());
    
    QFETCH(int, word_no);
    WordRow* row = FromXml::makeWordRow(xml_data, word_no);
    QVERIFY(row);
    
    QFETCH(PWordRow, expected);
    
    QCOMPARE(*row, *expected);
    
    delete row;
    delete expected;
}

void TestBitWinXml::testMakeFieldRow_data()
{
    QTest::addColumn<QDomElement>("xml_data");
    QTest::addColumn<int>("word_no");
    QTest::addColumn<PFieldRow>("expected");
    
    QDomDocument doc(createDoc(
        "<?xml version='1.0' encoding='UTF-8'?>"
        "<fields>"
            "<field title='Field No. -1' bits='7:0'/>"
            "<field title='Field No. 0' bits='7:5'/>"
            "<field title='Field #1' bits='3:6'/>"
            "<field title='Field-2' bits='4:4'/>"
            "<field title='Field No3' bits='3'/>"
            "<field bits='4:3'/>"
            "<field title='Field-5'/>"
        "</fields>"
    ));
    
    QDomElement el(doc.documentElement().firstChildElement("field"));
    QTest::newRow("Field [7:0]")
        << el << 1
        << new FieldRow("Field No. -1", BitRange(0, 8), 1)
    ;
    
    el = el.nextSiblingElement("field");
    QTest::newRow("Field [7:5]")
        << el << 0
        << new FieldRow("Field No. 0", BitRange(5, 8), 0)
    ;
    
    el = el.nextSiblingElement("field");
    QTest::newRow("Field [3:6]")
        << el << 4
        << new FieldRow("Field #1", BitRange(3, 6), 4)
    ;
    
    el = el.nextSiblingElement("field");
    QTest::newRow("Field [4:4]")
        << el << 2
        << new FieldRow("Field-2", BitRange(4, 5), 2)
    ;
    
    el = el.nextSiblingElement("field");
    QTest::newRow("Field [3]")
        << el << 1
        << new FieldRow("Field No3", BitRange(3, 4), 1)
    ;
    
    el = el.nextSiblingElement("field");
    QTest::newRow("Field with no title specified")
        << el << 5
        << new FieldRow(QString(), BitRange(3, 5), 5)
    ;
    
    el = el.nextSiblingElement("field");
    QTest::newRow("Field with no bits specified")
        << el << 10
        << new FieldRow("Field-5", BitRange(), 10)
    ;
}

void TestBitWinXml::testMakeFieldRow()
{
    QFETCH(QDomElement, xml_data);
    QVERIFY(!xml_data.isNull());
    
    QFETCH(int, word_no);
    
    FieldRow* row = FromXml::makeFieldRow(xml_data, word_no);
    QVERIFY(row);
    
    QFETCH(PFieldRow, expected);
    QCOMPARE(*row, *expected);
    
    delete row;
    delete expected;
}

void TestBitWinXml::testMakeBitsColumn_data()
{
    QTest::addColumn<QDomElement>("xml_data");
    QTest::addColumn<PBitsColumn>("expected");
    
    QDomDocument doc(createDoc(
        "<?xml version='1.0' encoding='UTF-8'?>"
        "<columns>"
            "<column title='Column #0' bits='7:0'/>"
            "<column title='Column No. 1' bits='5:1'/>"
        "</columns>"
    ));
    
    QDomElement el(doc.documentElement().firstChildElement("column"));
    QTest::newRow("Column [7:0]")
        << el
        << new BitsColumn("Column #0", BitRange(0, 8))
    ;
    
    el = el.nextSiblingElement("column");
    QTest::newRow("Column [5:1]")
        << el
        << new BitsColumn("Column No. 1", BitRange(1, 6))
    ;
}

void TestBitWinXml::testMakeBitsColumn()
{
    QFETCH(QDomElement, xml_data);
    QVERIFY(!xml_data.isNull());
    
    const BitsColumn* column =
        FromXml::makeBitsColumn(xml_data)
    ;
    QVERIFY(column);
    
    QFETCH(PBitsColumn, expected);
    QCOMPARE(*column, *expected);
    
    delete column;
    delete expected;
}

void TestBitWinXml::testMakeModelHeaders_data()
{
    QTest::addColumn<QDomElement>("xml_data");
    QTest::addColumn<QStringList>("row_headers");
    QTest::addColumn<QStringList>("column_headers");
    
    QDomDocument doc(createDoc(
        "<?xml version='1.0' encoding='UTF-8'?>"
        "<models>"
            "<no_rows>"
                "<columns>"
                    "<column title='Column #1' bits ='3:2'/>"
                    "<column title='Column No. 2' bits ='1:0'/>"
                "</columns>"
            "</no_rows>"
            "<no_columns>"
                "<rows>"
                    "<word title='Row-1'/>"
                    "<word title='Row2'/>"
                "</rows>"
            "</no_columns>"
            "<valid>"
                "<rows>"
                    "<header title='Top header'/>"
                    "<word title='Row #1'>"
                        "<field title='Row No. 2' bits='15:9'/>"
                        "<field title='Row-3' bits='8:0'/>"
                    "</word>"
                    "<header title='Bottom header'/>"
                "</rows>"
                "<columns>"
                    "<column title='Column-1' bits='15:11'/>"
                    "<column title='Column 2' bits='10:9'/>"
                    "<column title='Col. No. 3' bits='8:6'/>"
                    "<column title='Column #4' bits='5:0'/>"
                "</columns>"
            "</valid>"
        "</models>"
    ));
    
    QTest::newRow("empty DOM element")
        << QDomElement()
        << QStringList() << QStringList()
    ;
    
    QDomElement el(doc.documentElement().firstChildElement("no_rows"));
    QTest::newRow("Element with no rows")
        << el
        << QStringList()
        << (QStringList() << "Column #1" << "Column No. 2")
    ;
    
    el = el.nextSiblingElement("no_columns");
    QTest::newRow("Element with no columns")
        << el
        << (QStringList() << "Row-1" << "Row2")
        << QStringList()
    ;
    
    el = el.nextSiblingElement("valid");
    QTest::newRow("valid DOM element")
        << el
        << (
                QStringList()
                    << "Top header"
                    << "Row #1" << "Row No. 2" << "Row-3"
                    << "Bottom header"
            )
        << (
                QStringList()
                    << "Column-1" << "Column 2"
                    << "Col. No. 3" << "Column #4"
            )
    ;
}

void TestBitWinXml::testMakeModelHeaders()
{
    #define COMPARE_HEADERS(expected, model, orientation, role) \
    for (int i = 0; i < expected.size(); ++i) \
        QCOMPARE( \
            expected.value(i), \
            model->headerData(i, orientation, role).value<QString>() \
        );

    QFETCH(QDomElement, xml_data);
    QAbstractTableModel* model = FromXml::makeModel(xml_data);
    QVERIFY(model);
    
    QFETCH(QStringList, row_headers);
    QCOMPARE(model->rowCount(), row_headers.size());
    COMPARE_HEADERS(row_headers, model, Qt::Vertical, Qt::DisplayRole);
    
    QFETCH(QStringList, column_headers);
    QCOMPARE(model->columnCount(), column_headers.size());
    COMPARE_HEADERS(column_headers, model, Qt::Horizontal, Qt::DisplayRole);
    
    delete model;
    
    #undef COMPARE_HEADERS
}

void TestBitWinXml::testMakeModelData_data()
{
    QTest::addColumn<QStringList>("indata");
    QTest::addColumn<int>("row");
    QTest::addColumn<int>("column");
    QTest::addColumn<PRowText>("outdata_expected");
    
    QTest::newRow("1-st header, column 0")
        << QStringList()
        << 0 << 0 << LIST_TEXT("4")
    ;
    QTest::newRow("1-st header, column 1")
        << QStringList()
        << 0 << 1 << LIST_TEXT("1" << "2" << "3")
    ;
    QTest::newRow("1-st header, column 2")
        << QStringList()
        << 0 << 2 << LIST_TEXT("0")
    ;
    
    QTest::newRow("word 0, column 0")
        << (QStringList() << "abcde")
        << 1 << 0 << STRING_TEXT("e")
    ;
    QTest::newRow("word 0, column 1")
        << (QStringList() << "abcde")
        << 1 << 1 << STRING_TEXT("bcd")
    ;
    QTest::newRow("word 0, column 2")
        << (QStringList() << "abcde")
        << 1 << 2 << STRING_TEXT("a")
    ;
    
    QTest::newRow("word 0, field 0, column 0")
        << (QStringList() << "abcde")
        << 2 << 0 << STRING_TEXT("e")
    ;
    QTest::newRow("word 0, field 0, column 1")
        << (QStringList() << "abcde")
        << 2 << 1 << STRING_TEXT("  d")
    ;
    QTest::newRow("word 0, field 0, column 2")
        << (QStringList() << "abcde")
        << 2 << 2 << STRING_TEXT(" ")
    ;
    QTest::newRow("word 0, field 1, column 0")
        << (QStringList() << "abcde")
        << 3 << 0 << STRING_TEXT(" ")
    ;
    QTest::newRow("word 0, field 1, column 1")
        << (QStringList() << "abcde")
        << 3 << 1 << STRING_TEXT("bc ")
    ;
    QTest::newRow("word 0, field 1, column 2")
        << (QStringList() << "abcde")
        << 3 << 2 << STRING_TEXT("a")
    ;
    
    QTest::newRow("word 1, column 1, no data available")
        << (QStringList() << "abcde")
        << 5 << 1 << STRING_TEXT("   ")
    ;
    QTest::newRow("word 1, column 0")
        << (QStringList() << "abcde" << "fghjk")
        << 5 << 0 << STRING_TEXT("k")
    ;
    QTest::newRow("word 1, column 1")
        << (QStringList() << "abcde" << "fghjk")
        << 5 << 1 << STRING_TEXT("ghj")
    ;
    QTest::newRow("word 1, column 2")
        << (QStringList() << "abcde" << "fghjk")
        << 5 << 2 << STRING_TEXT("f")
    ;
    QTest::newRow("word 1, field 0, column 0")
        << (QStringList() << "abcde" << "fghjk")
        << 6 << 0 << STRING_TEXT("k")
    ;
    QTest::newRow("word 1, field 0, column 1")
        << (QStringList() << "abcde" << "fghjk")
        << 6 << 1 << STRING_TEXT("ghj")
    ;
    QTest::newRow("word 1, field 0, column 2")
        << (QStringList() << "abcde" << "fghjk")
        << 6 << 2 << STRING_TEXT(" ")
    ;
    QTest::newRow("word 1, field 1, column 0")
        << (QStringList() << "abcde" << "fghjk")
        << 7 << 0 << STRING_TEXT(" ")
    ;
    QTest::newRow("word 1, field 1, column 1")
        << (QStringList() << "abcde" << "fghjk")
        << 7 << 1 << STRING_TEXT("   ")
    ;
    QTest::newRow("word 1, field 1, column 2")
        << (QStringList() << "abcde" << "fghjk")
        << 7 << 2 << STRING_TEXT("f")
    ;
}

void TestBitWinXml::testMakeModelData()
{
    QDomDocument doc(createDoc(
        "<?xml version='1.0' encoding='UTF-8'?>"
        "<model>"
            "<rows>"
                "<header title='Bits:'/>"
                "<word title='word #1'>"
                    "<field title='field 1-1' bits='4:3'/>"
                    "<field title='field 1-2' bits='2:0'/>"
                "</word>"
                "<header title='bits'/>"
                "<word title='word #2'>"
                    "<field title='field 2-1' bits='4:1'/>"
                    "<field title='field 2-2' bits='0'/>"
                "</word>"
            "</rows>"
            "<columns>"
                "<column title='column 1' bits='4'/>"
                "<column title='column 2' bits='3:1'/>"
                "<column title='column 3' bits='0'/>"
            "</columns>"
        "</model>"
    ));
    
    Model* model = FromXml::makeModel(doc.documentElement());
    QVERIFY(model);
    
    QCOMPARE(model->rowCount(), 8);
    QCOMPARE(model->columnCount(), 3);
    
    QFETCH(QStringList, indata);
    model->setUp<PRowTextMaker::FromLiteral>(
        indata.begin(), indata.end()
    );
    
    QFETCH(int, row);
    QFETCH(int, column);
    QModelIndex index(model->index(row, column));
    QVERIFY(index.isValid());
    
    QVariant data(model->data(index, BitwiseDataRole));
    QVERIFY(data.isValid());
    QVERIFY(data.canConvert<PRowText>());
    
    QFETCH(PRowText, outdata_expected);
    QCOMPARE(
        data.value<PRowText>(),
        outdata_expected
    );
    
    delete model;
}


QTEST_MAIN(TestBitWinXml)


#include "main.moc"
